/*
 * Copyright (c) 2018 feeyo All Rights Reserved.
 * ProjectName: feeyoframework
 * FileName: AudioSaveHelper.kt
 * Auther: erik
 * Date: 12/26/18 4:51 PM
 * Version: 1.0
 * LastModified: 12/26/18 3:03 PM
 */

package com.feeyo.groundservice.easyphotoplus.audio

import android.content.Context
import android.media.AudioFormat
import android.os.Environment
import android.util.Log
import java.io.*
import java.nio.ByteBuffer
import java.nio.ByteOrder

class AudioSaveHelper {
    private var os: FileOutputStream? = null
    private var mFile: File? = null
    var mRecordSampleRate: Int = 0

    private fun prepareFolder(context: Context? = null): String {
        val storeLocation = context?.let { FileUtils.getDiskCacheDir(it) } ?: FileUtils.getFileCacheFolderPath()
        val folder = File("$storeLocation/voice")

        if (!folder.exists()) {
            folder.mkdirs()
        }
        return folder.absolutePath
    }

    fun getAudioPath(context: Context? = null, ext: String = Constants.AUDIO_RECORDER_FILE_EXT_WAV): String {
        val folder = prepareFolder(context)
        return "$folder/voice_" + System.currentTimeMillis() + ext
    }

    fun createNewFile(context: Context? = null) {
        Log.i("testing", "creating file")
        val storeLocation = context?.let { FileUtils.getDiskCacheDir(it) } ?: Environment.getExternalStorageDirectory().absolutePath
        val folder = File("$storeLocation/SoundRecorder")
        if (!folder.exists()) {
            folder.mkdir()
        }
        var count = 0
        do {
            count++
            val mFilePath = getAudioPath()
            mFile = File(mFilePath)
        } while (mFile!!.exists() && !mFile!!.isDirectory())

        try {
            os = FileOutputStream(mFile)
            writeWavHeader(
                os!!, Constants.RECORDER_CHANNELS, mRecordSampleRate,
                Constants.RECORDER_AUDIO_ENCODING
            )
        } catch (e: IOException) {
            // TODO: 4/9/17 handle this
            e.printStackTrace()
        }
    }


    /**
     * Writes the proper 44-byte RIFF/WAVE header to/for the given stream
     * Two size fields are left empty/null since we do not yet know the final stream size
     *
     * @param out The stream to write the header to
     * @param channelMask An AudioFormat.CHANNEL_* mask
     * @param sampleRate The sample rate in hertz
     * @param encoding An AudioFormat.ENCODING_PCM_* value
     * @throws IOException
     */
    @Throws(IOException::class)
    private fun writeWavHeader(out: OutputStream, channelMask: Int, sampleRate: Int, encoding: Int) {
        val channels: Short
        when (channelMask) {
            AudioFormat.CHANNEL_IN_MONO -> channels = 1
            AudioFormat.CHANNEL_IN_STEREO -> channels = 2
            else -> throw IllegalArgumentException("Unacceptable channel mask")
        }

        val bitDepth: Short
        when (encoding) {
            AudioFormat.ENCODING_PCM_8BIT -> bitDepth = 8
            AudioFormat.ENCODING_PCM_16BIT -> bitDepth = 16
            AudioFormat.ENCODING_PCM_FLOAT -> bitDepth = 32
            else -> throw IllegalArgumentException("Unacceptable encoding")
        }

        writeWavHeader(out, channels, sampleRate, bitDepth)
    }

    /**
     * Writes the proper 44-byte RIFF/WAVE header to/for the given stream
     * Two size fields are left empty/null since we do not yet know the final stream size
     *
     * @param out The stream to write the header to
     * @param channels The number of channels
     * @param sampleRate The sample rate in hertz
     * @param bitDepth The bit depth
     * @throws IOException
     */
    @Throws(IOException::class)
    private fun writeWavHeader(out: OutputStream, channels: Short, sampleRate: Int, bitDepth: Short) {
        // Convert the multi-byte integers to raw bytes in little endian format as required by the spec
        val littleBytes = ByteBuffer.allocate(14)
            .order(ByteOrder.LITTLE_ENDIAN)
            .putShort(channels)
            .putInt(sampleRate)
            .putInt(sampleRate * channels.toInt() * (bitDepth / 8))
            .putShort((channels * (bitDepth / 8)).toShort())
            .putShort(bitDepth)
            .array()

        // Not necessarily the best, but it's very easy to visualize this way
        out.write(
            byteArrayOf(
                // RIFF header
                'R'.toByte(), 'I'.toByte(), 'F'.toByte(), 'F'.toByte(), // ChunkID
                0, 0, 0, 0, // ChunkSize (must be updated later)
                'W'.toByte(), 'A'.toByte(), 'V'.toByte(), 'E'.toByte(), // Format
                // fmt subchunk
                'f'.toByte(), 'm'.toByte(), 't'.toByte(), ' '.toByte(), // Subchunk1ID
                16, 0, 0, 0, // Subchunk1Size
                1, 0, // AudioFormat
                littleBytes[0], littleBytes[1], // NumChannels
                littleBytes[2], littleBytes[3], littleBytes[4], littleBytes[5], // SampleRate
                littleBytes[6], littleBytes[7], littleBytes[8], littleBytes[9], // ByteRate
                littleBytes[10], littleBytes[11], // BlockAlign
                littleBytes[12], littleBytes[13], // BitsPerSample
                // data subchunk
                'd'.toByte(), 'a'.toByte(), 't'.toByte(), 'a'.toByte(), // Subchunk2ID
                0, 0, 0, 0
            )// Subchunk2Size (must be updated later)
        )
    }

    fun onDataReady(data: ByteArray) {
        os?.let {
            try {
                it.write(data, 0, data.size)
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }

    }

    /**
     * Updates the given wav file's header to include the final chunk sizes
     *
     * @param wav The wav file to update
     * @throws IOException
     */
    @Throws(IOException::class)
    private fun updateWavHeader(wav: File) {
        val sizes = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN)
            // There are probably a bunch of different/better ways to calculate
            // these two given your circumstances. Cast should be safe since if the WAV is
            // > 4 GB we've already made a terrible mistake.
            .putInt((wav.length() - 8).toInt()) // ChunkSize
            .putInt((wav.length() - 44).toInt()) // Subchunk2Size
            .array()

        var accessWave: RandomAccessFile? = null

        try {
            accessWave = RandomAccessFile(wav, "rw")
            // ChunkSize
            accessWave.seek(4)
            accessWave.write(sizes, 0, 4)

            // Subchunk2Size
            accessWave.seek(40)
            accessWave.write(sizes, 4, 4)
        } catch (ex: IOException) {
            // Rethrow but we still close accessWave in our finally
            throw ex
        } finally {
            if (accessWave != null) {
                try {
                    accessWave.close()
                } catch (ex: IOException) {
                    //
                }

            }
        }
    }

    fun onRecordingStopped() {
        try {
            os!!.close()
            updateWavHeader(mFile!!)
//            saveFileDetails(currentRecordTime)
            println("Record Complete. Saving and closing")
        } catch (e: IOException) {
            mFile!!.deleteOnExit()
            e.printStackTrace()
        }
    }

    var filePath: String? = null
        get() {
            return mFile?.absolutePath
        }

}